/**
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.inject.internal;

import static com.google.inject.internal.InternalFlags.getCustomClassLoadingOption;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.internal.InternalFlags.CustomClassLoadingOption;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.logging.Logger;

/**
 * Utility methods for runtime code generation and class loading. We use this stuff for {@link
 * net.sf.cglib.reflect.FastClass faster reflection}, {@link net.sf.cglib.proxy.Enhancer method
 * interceptors} and to proxy circular dependencies.
 *
 * <p>When loading classes, we need to be careful of:
 * <ul>
 *   <li><strong>Memory leaks.</strong> Generated classes need to be garbage collected in long-lived
 *       applications. Once an injector and any instances it created can be garbage collected, the
 *       corresponding generated classes should be collectable.
 *   <li><strong>Visibility.</strong> Containers like <code>OSGi</code> use class loader boundaries
 *       to enforce modularity at runtime.
 * </ul>
 *
 * <p>For each generated class, there's multiple class loaders involved:
 * <ul>
 *    <li><strong>The related class's class loader.</strong> Every generated class services exactly
 *        one user-supplied class. This class loader must be used to access members with private and
 *        package visibility.
 *    <li><strong>Guice's class loader.</strong>
 *    <li><strong>Our bridge class loader.</strong> This is a child of the user's class loader. It
 *        selectively delegates to either the user's class loader (for user classes) or the Guice
 *        class loader (for internal classes that are used by the generated classes). This class
 *        loader that owns the classes generated by Guice.
 * </ul>
 *
 * @author mcculls@gmail.com (Stuart McCulloch)
 * @author jessewilson@google.com (Jesse Wilson)
 */
public final class BytecodeGen {

    static final Logger logger = Logger.getLogger(BytecodeGen.class.getName());

    static final ClassLoader GUICE_CLASS_LOADER = canonicalize(BytecodeGen.class.getClassLoader());

    // initialization-on-demand...
    private static class SystemBridgeHolder {
        static final BridgeClassLoader SYSTEM_BRIDGE = new BridgeClassLoader();
    }

    /** ie. "com.google.inject.internal" */
    static final String GUICE_INTERNAL_PACKAGE = BytecodeGen.class.getName().replaceFirst("\\.internal\\..*$", ".internal");

    /*if[AOP]*/
    /** either "net.sf.cglib", or "com.google.inject.internal.cglib" */
    static final String CGLIB_PACKAGE = net.sf.cglib.proxy.Enhancer.class.getName().replaceFirst("\\.cglib\\..*$", ".cglib");

    static final net.sf.cglib.core.NamingPolicy FASTCLASS_NAMING_POLICY = new net.sf.cglib.core.DefaultNamingPolicy() {
        @Override
        protected String getTag() {
            return "ByGuice";
        }

        @Override
        public String getClassName(String prefix, String source, Object key, net.sf.cglib.core.Predicate names) {
            // we explicitly set the source here to "FastClass" so that our jarjar renaming
            // to $FastClass doesn't leak into the class names.  if we did not do this,
            // classes would end up looking like $$$FastClassByGuice$$, with the extra $
            // at the front.
            return super.getClassName(prefix, "FastClass", key, names);
        }
    };

    static final net.sf.cglib.core.NamingPolicy ENHANCER_NAMING_POLICY = new net.sf.cglib.core.DefaultNamingPolicy() {
        @Override
        protected String getTag() {
            return "ByGuice";
        }

        @Override
        public String getClassName(String prefix, String source, Object key, net.sf.cglib.core.Predicate names) {
            // we explicitly set the source here to "Enhancer" so that our jarjar renaming
            // to $Enhancer doesn't leak into the class names.  if we did not do this,
            // classes would end up looking like $$$EnhancerByGuice$$, with the extra $
            // at the front.
            return super.getClassName(prefix, "Enhancer", key, names);
        }
    };
    /*end[AOP]*/
    /*if[NO_AOP]
    private static final String CGLIB_PACKAGE = " "; // any string that's illegal in a package name
    end[NO_AOP]*/

    /**
     * Weak cache of bridge class loaders that make the Guice implementation
     * classes visible to various code-generated proxies of client classes.
     */
    private static final LoadingCache<ClassLoader, ClassLoader> CLASS_LOADER_CACHE;

    static {
        CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().weakKeys().weakValues();
        if (getCustomClassLoadingOption() == CustomClassLoadingOption.OFF) {
            builder.maximumSize(0);
        }
        CLASS_LOADER_CACHE = builder.build(new CacheLoader<ClassLoader, ClassLoader>() {
            @Override
            public ClassLoader load(final ClassLoader typeClassLoader) {
                logger.fine("Creating a bridge ClassLoader for " + typeClassLoader);
                return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                    public ClassLoader run() {
                        return new BridgeClassLoader(typeClassLoader);
                    }
                });
            }
        });
    }

    /**
     * Attempts to canonicalize null references to the system class loader.
     * May return null if for some reason the system loader is unavailable.
     */
    private static ClassLoader canonicalize(ClassLoader classLoader) {
        return classLoader != null ? classLoader : SystemBridgeHolder.SYSTEM_BRIDGE.getParent();
    }

    /**
     * Returns the class loader to host generated classes for {@code type}.
     */
    public static ClassLoader getClassLoader(Class<?> type) {
        return getClassLoader(type, type.getClassLoader());
    }

    private static ClassLoader getClassLoader(Class<?> type, ClassLoader delegate) {

        // simple case: do nothing!
        if (getCustomClassLoadingOption() == CustomClassLoadingOption.OFF) {
            return delegate;
        }

        // java.* types can be seen everywhere
        if (type.getName().startsWith("java.")) {
            return GUICE_CLASS_LOADER;
        }

        delegate = canonicalize(delegate);

        // no need for a bridge if using same class loader, or it's already a bridge
        if (delegate == GUICE_CLASS_LOADER || delegate instanceof BridgeClassLoader) {
            return delegate;
        }

        // don't try bridging private types as it won't work
        if (Visibility.forType(type) == Visibility.PUBLIC) {
            if (delegate != SystemBridgeHolder.SYSTEM_BRIDGE.getParent()) {
                // delegate guaranteed to be non-null here
                return CLASS_LOADER_CACHE.getUnchecked(delegate);
            }
            // delegate may or may not be null here
            return SystemBridgeHolder.SYSTEM_BRIDGE;
        }

        return delegate; // last-resort: do nothing!
    }

    /*if[AOP]*/
    // use fully-qualified names so imports don't need preprocessor statements 
    public static net.sf.cglib.reflect.FastClass newFastClass(Class<?> type, Visibility visibility) {
        net.sf.cglib.reflect.FastClass.Generator generator = new net.sf.cglib.reflect.FastClass.Generator();
        generator.setType(type);
        if (visibility == Visibility.PUBLIC) {
            generator.setClassLoader(getClassLoader(type));
        }
        generator.setNamingPolicy(FASTCLASS_NAMING_POLICY);
        logger.fine("Loading " + type + " FastClass with " + generator.getClassLoader());
        return generator.create();
    }

    public static net.sf.cglib.proxy.Enhancer newEnhancer(Class<?> type, Visibility visibility) {
        net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();
        enhancer.setSuperclass(type);
        enhancer.setUseFactory(false);
        if (visibility == Visibility.PUBLIC) {
            enhancer.setClassLoader(getClassLoader(type));
        }
        enhancer.setNamingPolicy(ENHANCER_NAMING_POLICY);
        logger.fine("Loading " + type + " Enhancer with " + enhancer.getClassLoader());
        return enhancer;
    }
    /*end[AOP]*/

    /**
     * The required visibility of a user's class from a Guice-generated class. Visibility of
     * package-private members depends on the loading classloader: only if two classes were loaded by
     * the same classloader can they see each other's package-private members. We need to be careful
     * when choosing which classloader to use for generated classes. We prefer our bridge classloader,
     * since it's OSGi-safe and doesn't leak permgen space. But often we cannot due to visibility.
     */
    public enum Visibility {

        /**
         * Indicates that Guice-generated classes only need to call and override public members of the
         * target class. These generated classes may be loaded by our bridge classloader.
         */
        PUBLIC {
            @Override
            public Visibility and(Visibility that) {
                return that;
            }
        },

        /**
         * Indicates that Guice-generated classes need to call or override package-private members.
         * These generated classes must be loaded in the same classloader as the target class. They
         * won't work with OSGi, and won't get garbage collected until the target class' classloader is
         * garbage collected.
         */
        SAME_PACKAGE {
            @Override
            public Visibility and(Visibility that) {
                return this;
            }
        };

        public static Visibility forMember(Member member) {
            if ((member.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) {
                return SAME_PACKAGE;
            }

            Class[] parameterTypes;
            if (member instanceof Constructor) {
                parameterTypes = ((Constructor) member).getParameterTypes();
            } else {
                Method method = (Method) member;
                if (forType(method.getReturnType()) == SAME_PACKAGE) {
                    return SAME_PACKAGE;
                }
                parameterTypes = method.getParameterTypes();
            }

            for (Class<?> type : parameterTypes) {
                if (forType(type) == SAME_PACKAGE) {
                    return SAME_PACKAGE;
                }
            }

            return PUBLIC;
        }

        public static Visibility forType(Class<?> type) {
            return (type.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) != 0 ? PUBLIC : SAME_PACKAGE;
        }

        public abstract Visibility and(Visibility that);
    }

    /**
     * Loader for Guice-generated classes. For referenced classes, this delegates to either either the
     * user's classloader (which is the parent of this classloader) or Guice's class loader.
     */
    private static class BridgeClassLoader extends ClassLoader {

        BridgeClassLoader() {
            // use system loader as parent
        }

        BridgeClassLoader(ClassLoader usersClassLoader) {
            super(usersClassLoader);
        }

        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

            if (name.startsWith("sun.reflect")) {
                // these reflection classes must be loaded from bootstrap class loader
                return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
            }

            if (name.startsWith(GUICE_INTERNAL_PACKAGE) || name.startsWith(CGLIB_PACKAGE)) {
                if (null == GUICE_CLASS_LOADER) {
                    // use special system bridge to load classes from bootstrap class loader
                    return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
                }
                try {
                    Class<?> clazz = GUICE_CLASS_LOADER.loadClass(name);
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                } catch (Throwable e) {
                    // fall-back to classic delegation
                }
            }

            return classicLoadClass(name, resolve);
        }

        // make the classic delegating loadClass method visible
        Class<?> classicLoadClass(String name, boolean resolve) throws ClassNotFoundException {
            return super.loadClass(name, resolve);
        }
    }
}
